/*
    This file is part of jTopaz.
    http://gcl.cis.udel.edu/projects/jtopaz
    Copyright (C) 2009 University of Delaware

    jTopaz is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    jTopaz is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of                                                        
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with jTopaz.  If not, see <http://www.gnu.org/licenses/>.
*/

if( typeof( SDSC_FILE_LIB ) == "undefined" )
{

  try {

    eval("const SDSC_FILE_LIB = true;");
    eval('const S_FILE_PATH = "native"');
    eval('const S_URI = "uri"');

    }catch( e ){ alert( e ) }
}

/**
 *@constructor
 *@param path of file to create
 */
function File( aPath )
{
    // private members and functions here
	try{

	    this.init( aPath );

	}catch( e ){ alert( e ); }
}



File.prototype = 
{


// Console Service: for writing to the Javascript Console
    /** @private */
    consoleService : null,
    /** @private */
    fileProtocolHandler : null,
    /** @private */
    mPath:	null,
    /** @private */
    mFile:	null,
    /** @private */
    scriptStream:	null,
    /** @private */
    fileStream:	null,
    /** @private */
    fileOutStream:	null,
    /** @private */
    inStream:	null,
    /** @private */
    isOpen:	null,
    /** @private */
    lineBuffer:	null,

    /** @private */
    b64encode: function( data )
    {
	/*
	*   Base64.js
	*
	*   copyright 2003, Kevin Lindsey
	*   licensing info available at: http://www.kevlindev.com/license.txt
	*****/
	/*
	*   encoding table
	*****/
	var encoding = [
	    "A", "B", "C", "D", "E", "F", "G", "H",
	    "I", "J", "K", "L", "M", "N", "O", "P",
	    "Q", "R", "S", "T", "U", "V", "W", "X",
	    "Y", "Z", "a", "b", "c", "d", "e", "f",
	    "g", "h", "i", "j", "k", "l", "m", "n",
	    "o", "p", "q", "r", "s", "t", "u", "v",
	    "w", "x", "y", "z", "0", "1", "2", "3",
	    "4", "5", "6", "7", "8", "9", "+", "/"
	];


	var result = [];
	var ip57   = Math.floor(data.length / 57);
	var fp57   = data.length % 57;
	var ip3    = Math.floor(fp57 / 3);
	var fp3    = fp57 % 3;
	var index  = 0;
	var num;
	
	for ( var i = 0; i < ip57; i++ ) {
	    for ( var j = 0; j < 19; j++, index += 3 ) {
		num = data[index] << 16 | data[index+1] << 8 | data[index+2];
		result.push(encoding[ ( num & 0xFC0000 ) >> 18 ]);
		result.push(encoding[ ( num & 0x03F000 ) >> 12 ]);
		result.push(encoding[ ( num & 0x0FC0   ) >>  6 ]);
		result.push(encoding[ ( num & 0x3F     )       ]);
	    }
    //        result.push("\n");
	}

	for ( i = 0; i < ip3; i++, index += 3 ) {
	    num = data[index] << 16 | data[index+1] << 8 | data[index+2];
	    result.push(encoding[ ( num & 0xFC0000 ) >> 18 ]);
	    result.push(encoding[ ( num & 0x03F000 ) >> 12 ]);
	    result.push(encoding[ ( num & 0x0FC0   ) >>  6 ]);
	    result.push(encoding[ ( num & 0x3F     )       ]);
	}

	if ( fp3 == 1 ) {
	    num = data[index] << 16;
	    result.push(encoding[ ( num & 0xFC0000 ) >> 18 ]);
	    result.push(encoding[ ( num & 0x03F000 ) >> 12 ]);
	    result.push("==");
	} else if ( fp3 == 2 ) {
	    num = data[index] << 16 | data[index+1] << 8;
	    result.push(encoding[ ( num & 0xFC0000 ) >> 18 ]);
	    result.push(encoding[ ( num & 0x03F000 ) >> 12 ]);
	    result.push(encoding[ ( num & 0x0FC0   ) >>  6 ]);
	    result.push("=");
	}

	return result.join("");
    },
	

    /**
    *init
    *@param aeither a string file uri path, nsIURI with 'file' scheme, or
    * native uri string
    *@private
    */
    init: function( arg )
    {

	var rv;
	try{
	    this.consoleService = Components.classes['@mozilla.org/consoleservice;1'].getService(Components.interfaces.nsIConsoleService);
	    this.fileProtocolHandler = Components.classes["@mozilla.org/network/protocol;1?name=file"].createInstance(Components.interfaces.nsIFileProtocolHandler);


	    // in case we're reinitializing
	    this.mFile = null;

	    if( typeof( arg ) == "object" )
	    {
		if( /nsIURI/.test( arg ) )
		{
		    //only deal with file uris
		    if( arg.scheme == "file" )
		    {
			this.mFile = this.fileProtocolHandler.getFileFromURLSpec( arg.asciiSpec );
			rv = true;
		    }

		}
		else if( /nsIFile/.test( arg ) )
		{
			this.mFile = arg.clone( ); 
			rv = true;
		}
		else if( /nsILocalFile/.test( arg ) )
		{
			this.mFile = arg.clone( ); 
			rv = true;
		}
		else
		{
		    // assume that it's another file object
                               this.mFile =  Components.classes["@mozilla.org/file/local;1"]
                                    .createInstance(Components.interfaces.nsILocalFile);
                               this.mFile.initWithPath( arg.getPath( "native"  ) );
                                rv = true;
		}

	    }
	    else if( typeof( arg ) == "string" )
	    {
			// will work if it is a proper file url already
			try{ 
			    this.mFile = this.fileProtocolHandler.getFileFromURLSpec( arg );
			}catch( e ){ }
			    
			if( this.mFile == null )
			{
			    // will work if string is a natively formatted uri 
			    try{ 
			       this.mFile =  Components.classes["@mozilla.org/file/local;1"]
				    .createInstance(Components.interfaces.nsILocalFile);
			       this.mFile.initWithPath( arg ); 
				rv = true;
    
			    }catch( e ){ rv = false; }
			}
			else rv = true;

			if( this.mFile == null )
			    rv = false;
			
			//uri = null;
	    }

	//}catch( e ){ rv = false; }
	}catch( e ){ throw e  }

	return rv;

    },

    /**
    *@return {nsIFIle} mozilla file object
    */
    getNSIFile:	function( )
    {
	return this.mFile;
    },

    /**
    *@param {string} type specifying format of path to return. Either
    *	"native": platorm native path; "uri": an nsIURI object; "": uri as a string
    *@return {Object} path of this file
    */
    getPath:	function( type )
    {

	    if(  this.mFile == null  )
		return null;
	    //if( ! this.exists( )  )
	//	return null;
		
	    try{ 
		if( (type == undefined) || ( type == "" )  || (type == null)  )
		{
		    // returns fileURI string
		    return this.fileProtocolHandler.getURLSpecFromFile( this.mFile )
		}
		else if( type == "native" )
		{
		    // returns native path string
		    return this.mFile.path;
		}
		else if( type == "uri" )
		{
		    // returns a URI object
		    return this.fileProtocolHandler.newFileURI( this.mFile )
		}
	    }catch( e ){ return null }


    },

    /**
    *Whether the file exists in the filesystem or not
    *@return {boolean} whether the file exists or not
    */
    exists:	function(  )
    {
	if( this.mFile == null )
	    throw new ReferenceError( "File Not Initialized" );
	    //return false;

	var rv;
	try{
	    rv = this.mFile.exists( );
	}catch( e ){ rv = false }
	return rv
    },

    /**
    *Tests whether this object represents a file.
    *@return {boolean}
    */
    isFile:	function( )
    {
//	if( ! this.exists( ) )
//	    return false;

	var rv;
	try{
	    rv = this.mFile.isFile( );
	}catch( e ){ rv = false }
	return rv
    },

    /**
    *Tests whether this object represents a directory.
    *Requires file to exist.
    *@return {boolean}
    */
    isDir:	function( )
    {
//	if( ! this.exists( ) )
//	    return false;

	var rv;
	try{
	    rv = this.mFile.isDirectory( );
	}catch( e ){ rv = false }
	return rv
    },

    /**
    *Tests whether this file can be read with the current permissions.
    *Requires file to exist.
    *@return {boolean}
    */
    canRead:	function( )
    {
//	if( ! this.exists( ) )
//	    return false;

	var rv;
	try{
	    rv = this.mFile.isReadable( );
	}catch( e ){ rv = false }
	return rv
    },

    /**
    *Tests whether this file can be written to with the current permissions.
    *Requires file to exist.
    *@return {boolean}
    */
    canWrite:	function( )
    {
//	if( ! this.exists( ) )
//	    return false;

	var rv;
	try{
	    rv = this.mFile.isWritable( );
	}catch( e ){ rv = false }
	return rv
    },

    /**
    *Returns everything after the path 
    *@return {string}
    */
    getLeaf:	function( )
    {
	if( this.mFile == null )
	    return false;

	var rv;
	try{
	    rv = this.mFile.leafName;
	}catch( e ){ rv = ""}
	return rv
    },

    /**
    * Returns everything after the path and before the extension.
    * If there is no extension or if the file is hidden, this is the 
    * same as {@link #getLeaf}
    *@return {string}
    */
    getName:	function( )
    {
	if( this.mFile == null )
	    return "";

	var rv;
	try{
	    rv = this.mFile.leafName;
	    var i = rv.indexOf( "." );
    
	    rv = ( i <= 0 ) ? rv : rv.substring( 0 , i ); 

	}catch( e ){ rv = "" }
	return rv
    },

    /**
    * Returns everything after the name. 
    * An extension begins after the first instance of '.'.
    * If there is no extension, the empty string "" is returned. 
    *@return {string}
    */
    getExtension:	function( )
    {
	if( this.mFile == null )
	    return false;

	var rv;
	try{
	    rv = this.mFile.leafName;
	    var i = rv.indexOf( "." );

	    rv = ( (i == rv.length - 1) || ( i == -1 ) ) ? "" : rv.substring( i + 1  ); 

	}catch( e ){ rv = ""}
	return rv
    },

    /**
    * Returns the permissions of the current file object.
    * Requires file to exist.
    *@return {int}  the integer 10 prepended to the hexadecimal permissions value
    */
    getPermissions:	function( )
    {
//	if( ! this.exists( ) )
//	    return false;
	
	var rv;
	try{
	    rv =  this.mFile.permissions;
	    rv  = parseInt( rv.toString( 8 ) );
	}catch( e ){ rv = null;}
	return rv;
    },

    /**
    * Sets permissions of the current file object.
    * Requires file to exist.
    *@param {int} arg A hexadecimal value: eg 0664
    *@return {void}
    */
    setPermissions:	function( arg )
    {
//	if( ! this.exists( ) )
//	    return false;
	
	var rv;
	try{
	    this.mFile.permissions = arg ;
	    rv = true;
	}catch( e ){ rv = false }
	return rv;
    },

    /**
    * Returns the the last time this file object was modified
    * Requires file to exist.
    *@return {int} number of milliseconds from midnight (00:00:00), January 1, 1970 Greenwich Mean Time (GMT).
    */
    getLastModified:	function( )
    {
//	if( ! this.exists( ) )
//	    return false;
	
	var rv;
	try{
	    rv =  this.mFile.lastModifiedTime;
	}catch( e ){ rv = ""}
	return rv;
    },


    /**
    * Creates the file in the filesystem. 
    *@param {string} arg "dir": to create a directory; "file" to create a file.
    *@return {void} 
    */
    create:	function( type )
    {
	if( this.mFile == null )
	    return false;

	try{
	    // lets try to create it first
		if( type )
		{
		    if( type == "dir" )
			this.mFile.create( Components.interfaces.nsIFile.DIRECTORY_TYPE, 0664);
		    else if( type == "file" )
			this.mFile.create( Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0664);
		}
		else
		    this.mFile.create( Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0664);
	}catch( e ){ alert( e )  } 

	return null;

    },


    /**
    * Prepares the current file object for reading or writing
    *@param {string} arg "r" for read, "w" for write, "a" for append  
    *@return {boolean} success
    */
    open:	function( arg )
    {
	var rv;
	if( this.mFile == null )
	     rv = false;

	try{
	    if( !this.exists( ) )
		rv = false;
	    else rv = true;
	}catch( e ){ rv = false; }


	if( !rv )
	{
	    try{
	    // lets try to create it first
		this.mFile.create( Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0664);
	    }catch( e ){ rv = false; return rv; } 
	}

	try{

	    // close open streams;
	    this.close();


	    if( arg == "r" )
	    {
		/*
		    deal with
		     void init ( nsIFile file , PRInt32 ioFlags , PRInt32 perm , PRInt32 behaviorFlags )

		Arguments:
		    file: file to read from (must QI to nsILocalFile)
		    ioFlags: file open flags listed in prio.h
		    perm: file mode bits listed in prio.h
		    behaviorFlags: flags specifying various behaviors of the class (see enumerations in the class) 
		*/

		this.fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
					.createInstance(Components.interfaces.nsIFileInputStream);
		this.scriptStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
					.createInstance(Components.interfaces.nsIScriptableInputStream);
		this.fileStream.init( this.mFile, 1, 0, false);
		this.scriptStream.init(this.fileStream); 


		this.lineBuffer = new Array( );
		this.mPosition =  0;
		rv = true;
		
	    }
	    else if( arg == "w" )
	    {
//http://developer.mozilla.org/en/docs/Writing_textual_data
		this.fileOutStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
                         .createInstance(Components.interfaces.nsIFileOutputStream);
		this.fileOutStream.init( this.mFile, 0x02 | 0x08 | 0x20, 0664, 0);
		rv = true;

	    }
	    else if( arg == "a" )
	    {
		this.fileOutStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
                         .createInstance(Components.interfaces.nsIFileOutputStream);
		this.fileOutStream.init( this.mFile, 0x02 | 0x10 , 0664, 0);
		rv = true;

	    }
	    else rv = false;

	}catch( e ){ rv = false }

	if( rv )
	    this.isOpen = true;
	else  this.isOpen = false;

	return rv;

    },

    /**
    * Closes the current file object to further reading or writing.
    * Requires file to exist
    *@return {void}
    */
    close:	function( )
    {
	
	// file unititialized error
	if( ! this.exists( ) )
	    return false;

	this.lineBuffer = null;

	var rv;
	try{

		if( this.fileStream != null )
		    this.fileStream.close( );
		if( this.scriptStream != null )
		    this.scriptStream.close( );
		if( this.fileOutStream != null )
		    this.fileOutStream.close( );
	}catch( e ){ }
		this.fileStream = null;
		this.scriptStream = null;
		this.fileOutStream = null;

		this.isOpen = false;
		rv = true;
	
	return rv;

    },

    /**
    * Performs a read on the underlying file object.
    * File must exist and be {@link #open} for reading.
    *@see #open
    *@param {int} size How much to read, assumed to be nsIFile.fileSize if
    *	not specified
    *@return {string} Data (in string form) from the file
    */
    read:	function( size )
    {
	
	if( ! this.exists( ) )
	    return false;

	var rv;
    
	try{

	    // maybe check if streams are initalized before causing an err
	    if( size )
		rv = this.scriptStream( size );
	    else
		rv = this.scriptStream.read( this.mFile.fileSize );


	}catch( e ){ rv = null;  }


	return rv;
    }, 

    /**
    * Performs a write on the underlying file object.
    * File must exist and be {@link #open} for writing or appending.
    *@see #open
    *@param {string}  What to write
    *@return {boolean} true if successful
    */
    write:	function( arg )
    {

//	if( ! this.exists( ) )
//	    return false;

	var rv;
	try{

	    if( this.fileOutStream == null )
		rv = false;
	    else
	    {
		this.fileOutStream.write( arg , arg.length );
		rv = true;
	    }
	
	}catch( e ){ rv = false; }

	return rv;
    },

    /**
    * Reads a single line of string text terminated by a CR, LF, CRLF, LFCR, or eof. 
    * The line terminator is not returned. If another type of read operation has
    *  or will be performed, a {@link #close} and {@link #open} must be done first. 
    *@return {string} A line of data from the file
    */
    readLine:	function( )
    {

	if( ! this.isOpen )
	    return false;

	var rv;
	try{

	    if( this.fileStream == null )
		rv = null;
	    else
	    {
	    
/*
    * ***This doesn't work correctly when the last line doesn't have a line terminator
		this.fileStream.QueryInterface(Components.interfaces.nsILineInputStream);
		rv  = { };
		if( !this.fileStream.readLine( rv ) )
		{
		    rv = null;	
		}
		else{ rv = rv.value; }
*/
		// from jslib. the other way didn't handle all cases
		var buf     = null;
		var tmp     = null;
		if (this.lineBuffer.length < 2)
		{
		    buf = this.scriptStream.read( 1024 );
		    this.mPosition = this.mPosition + 1024;
		    if (this.mPosition > this.mFile.fileSize)
			this.mPosition  = this.mFile.fileSize;
		    if (buf) 
		    {
			if (this.lineBuffer.length == 1) 
			{
			    tmp = this.lineBuffer.shift();
			    buf = tmp+buf;
			}
			this.lineBuffer = buf.split(/[\n\r]/);
		    }
		}
		rv = this.lineBuffer.shift();
	    }
	
	}catch( e ){  rv = null;  }
	return rv;

    },

    /**
    * Reads all lines of the file into an Array object
    *  If another type of read operation has or will be performed, a {@link #close} 
    * and {@link #open} must be done first. 
    *@return {array} All lines of data from the file
    */
    readAllLines:	function( )
    {

	var lines = new Array( );

	var line = this.readLine( );
	
	while( line != null )
	{
	    lines.push( line );
	    line = this.readLine( );
	}

	return lines;	

    },

    /**
    * Read the file as an array of bytes.
    * File must exist and be {@link #open} for reading.
    *@return {array} An Array of bytes
    */
    readByteArray:	function( )
    {

	if( ! this.exists( ) )
	    return false;

	var rv;
	try{

	    if( this.fileStream == null )
		rv = null;
	    else
	    {

		var binaryStream = Components.classes['@mozilla.org/binaryinputstream;1']
		   .createInstance(Components.interfaces.nsIBinaryInputStream);

		binaryStream.setInputStream( this.fileStream );
		rv = binaryStream.readByteArray( this.fileStream.available( ) );

	    }

	}catch( e ){ rv = null;  }

	return rv;

    },


    /**
    * Moves the file to a location specifed.
    * Requires the file to exist. 
    *@param {Object} arg nsIFile, nsIURI, string path, or another File object where the current  file will be moved to
    *@return {boolean} if the move was successful
    */
    // TODO: support arg being another File object (in init)
    move:	function( arg )
    {
	if( ! this.exists( ) )
	    return false;

	if( (arg == null) || (arg == "" ) )
	    return false;

	var rv;
	try{

	    var oldFilePath = this.mFile.path;
	    
	    rv = this.init( arg );
	
	    if( rv )
	    {
		if( this.mFile.exists( ) )
		{
		    // already exists, whoops. offer option to overwrite?
		    rv = false;
		}
		else
		{
		   var oldFile =  Components.classes["@mozilla.org/file/local;1"]
			.createInstance(Components.interfaces.nsILocalFile);
		   oldFile.initWithPath( oldFilePath ); 

		   oldFile.moveTo( this.mFile.parent , this.mFile.leafName );

		    rv = true;
		}
	    }
	    else
	    {
	       this.mFile =  Components.classes["@mozilla.org/file/local;1"]
		    .createInstance(Components.interfaces.nsILocalFile);
	       this.mFile.initWithPath( oldFilePath  ); 
		rv = false;
	    }


	}catch( e ){ rv = false;  }

	return rv;

    },

    /**
    * Renames the file.
    * Requires the file to exist. 
    *@param {string} arg new name. 
    *@return {boolean} if the move was successful
    */
    rename:	function( arg )
    {
	if( ! this.exists( ) )
	    return false;

	if( (arg == null) || (arg == "" ) || !(typeof( arg ) == "string" ) )
	    return false;

	var rv;
	try{
	    var parent = this.mFile.parent;
	    parent.append( arg );
	    rv = this.move( parent.path );  
	}catch( e ){ rv = false;  alert( e ) }

	return rv;
    },

    /**
    * Returns a {@link File} instance of this object's parent
    *@return {File} the parent File object
    */
    getParent:	function( )
    {
	if( this.mFile == null )
	    return null;

	var rv;
	try{
	    rv = new File( this.mFile.parent );
	}catch( e ){ rv = null;  }

	return rv;

    },
    

    /**
    * Removes the current file from the filesystem.
    * Requires the file to exist.
    *@return {boolean} whether the operation was successful
    */
    remove:	function( )
    {
	if( ! this.exists( ) )
	    return false;

	var rv;
	try{
	    this.mFile.remove( false );
	    rv = true;
	}catch( e ){ rv = false;  }

	return rv;

    },

    /**
    * Constructs a descendent of the current file.
    * This operation only has meaning if the current file either
    * doesn't exist or is a directory.
    *@param {string} arg name of the new descendent.
    *@return {boolean} whether the operation was successful
    */
    append:	function(  arg )
    {
	if( ! this.exists( ) )
	    return false;

	if( typeof( arg ) != "string" )
	    return false;

	var rv;
	try{
	    this.mFile.append( arg );
	    rv = true;
	}catch( e ){ rv = false; } 

	return rv;

    },


    /**
    * Read the entire file with Base64 encoding.
    * The file is read as a single line, not 80 character
    * \n terminated lines. File must exist and be {@link #open} for reading.
    *@see #readByteArray
    *@return {string} the file's contents Base64 encoded
    */
    readBase64:	function( )
    {
	
	if( ! this.exists( ) )
	    return false;

	var rv;

	var hasComponent = false;
	try{
		
		 var b64 = Components.classes["@mozilla.org/base64_component;1"].createInstance(Components.interfaces.IBase64);
		 //var start = new Date( ).getTime( );
		 //var inText = this.read( );
		 //rv = b64.encodeString( inText );
		 rv = b64.encodeFile( this.getNSIFile( ) );
		//var end = new Date( ).getTime( );
		//consoleService.logStringMessage( "XPCOM Time: " + (end - start) );
		 hasComponent = true;

	}catch( e ){ hasComponent = false; }

	if( !hasComponent )
	{
	    try{
		   //var start = new Date( ).getTime( );
		    rv = this.readByteArray( );
		    if( (rv != null) && (rv != "" ) )
		    {
			rv = this.b64encode( rv );
		    }
		    //var end = new Date( ).getTime( );
		    //consoleService.logStringMessage( "Javascript Time: " + (end - start) );

	    }catch( e ){ rv = null;  }
	}

	return rv;


    }


};


/** @type int */
File.prototype.__defineGetter__( 'lastModified' , function(){ return this.getLastModified( ) } ) 

/** @type int */
File.prototype.__defineGetter__( 'permissions' , function(){ return this.getPermissions( ) } ) 
File.prototype.__defineSetter__( 'permissions' , function( arg ){ return this.setPermissions( arg ) } ) 

/** @type string  @see  #getExtension */
File.prototype.__defineGetter__( 'ext' , function(){ return this.getExtension( ) } ) 
/** @type string  @see  #getLeaf */
File.prototype.__defineGetter__( 'leaf' , function(){ return this.getLeaf( ) } ) 
/** @type string @see  #getName */
File.prototype.__defineGetter__( 'name' , function(){ return this.getName( ) } ) 
/** @type nsIFile @see #getNSIFile */
File.prototype.__defineGetter__( 'nsIFile' , function(){ return this.getNSIFile( ) } ) 
/** @type string @see #getPath */
File.prototype.__defineGetter__( 'path' , function(){ return this.getPath( ) } ) 